module Main where

import Graphics.UI.WX
import System.Random

------------------------------------------------------------------------

{-
Some internal constants.

The mine field's size is sizeX x sizeY.
The number of mines is mines.
-}

sizeX, sizeY, mines :: Int
sizeX  = 25
sizeY  = 13
mines  = 40

------------------------------------------------------------------------

main :: IO ()
main = start mineFrame

-- mineFrame generates random mine positions and creates a window
-- representing the mine field
mineFrame :: IO ()
mineFrame =
  do frm <- frame [ text := "Mine Sweeper" ]
     mns <- placeMines
     fld <- field frm mns
     cls <- button frm [ text := "Close", on command := close frm ]
     set frm [ layout :=
                 column 5
                 [ grid 1 1
                   [ [ widget (but cl)
                     | cl <- row
                     ]
                   | row <- fld
                   ]
                 , floatCenter (widget cls)
                 ]
             ]

------------------------------------------------------------------------

-- a position is a pair of integers, representing the coordinates
type Pos = (Int,Int)

-- placeMines creates mines at random valid positions
placeMines :: IO [Pos]
placeMines =
  do sequence [ do i <- randomRIO (0,sizeX-1)
                   j <- randomRIO (0,sizeY-1)
                   return (i,j)
              | k <- [1..mines]
              ]

------------------------------------------------------------------------

{-
Status: a datatype for the status of a cell.

A cell can be Hidden (nothing is known yet), Cleared (there was no mine),
Mine (there was a mine), and Dead (a mine has been discovered somewhere
else and now the cell is disabled).
-}

data Status
  = Hidden
  | Cleared
  | Mine
  | Dead
 deriving ( Show, Eq )

-- colr st returns the color associated with the status st
colr :: Status -> Color
colr Hidden  = grey
colr Cleared = white
colr Mine    = red
colr Dead    = yellow

------------------------------------------------------------------------

{-
Cell: a datatype for cells.

A cell contains its position, the button that represents it on the
screen, and the variable that contains its status.
-}

data Cell = Cell{ pos :: Pos, but :: Button (), var :: Var Status }

-- cell frm p creates a cell with position p
cell :: Frame () -> Pos -> IO Cell
cell frm p =
  do but <- smallButton frm [ fontWeight := WeightBold ]
     var <- variable []
     let cl = Cell p but var
     setStatus cl Hidden
     return cl

-- getStatus cl returns the status of the cell cl
getStatus :: Cell -> IO Status
getStatus (Cell _ _ var) =
  do get var value

-- setStatus cl st changes the status of the cell cl to st
setStatus :: Cell -> Status -> IO ()
setStatus (Cell _ but var) st =
  do set var [ value := st ]
     set but [ bgcolor := colr st ]

-- setClick cl instr associates the instructions instr with the cell cl
setClick :: Cell -> IO () -> IO ()
setClick (Cell _ but _) instr =
  do set but [ on command := instr ]

-- setText cl s changes the text in cell cl to s
setText :: Cell -> String -> IO ()
setText (Cell _ but _) s =
  do set but [ text := s ]

------------------------------------------------------------------------

-- a field is a matrix of cells
type Field = [[Cell]]

-- field creates a matrix of cells, associating the instructions "clicked"
-- with each cell
field :: Frame () -> [Pos] -> IO Field
field frm mns =
  do fld <- sequence
              [ sequence
                  [ cell frm (i,j)
                  | i <- [0..sizeX-1]
                  ]
              | j <- [0..sizeY-1]
              ]
     sequence_
       [ setClick cl (clicked cl fld mns)
       | row <- fld
       , cl  <- row
       ]
     return fld

------------------------------------------------------------------------

{-
clicked cl fld mns is executed when a user clicks on the cell cl:
one of three things happens:

* nothing happens because the cell has already been clicked

* there is a mine in the cell, all mines are revealed and the board
  is disabled

* there is no mine, and the number of surrounding mines is calculated.
  if there are no surrounding mines, all surrounding cells are
  automatically clicked as well
-}

clicked :: Cell -> Field -> [Pos] -> IO ()
clicked me fld mns =
  do st <- getStatus me
     case st of
       Hidden | pos me `elem` mns ->
         do sequence_
              [ setStatus cl (if pos cl `elem` mns then Mine else Dead)
              | row <- fld
              , cl  <- row
              ]
         
       Hidden ->
         do setStatus me Cleared
            if n == 0 then
              do sequence_
                   [ clicked cl' fld mns
                   | (i',j') <- sur
                   , let cl' = fld !! j' !! i'
                   ]
             else
              do setText me (show n)
              
        where
         sur = surrounds (pos me)
         n   = length [ p | p <- sur, p `elem` mns ]
       
       _ ->
         do return ()

------------------------------------------------------------------------

-- surrounds p computes all valid positions that surround the position p
surrounds :: (Int,Int) -> [(Int,Int)]
surrounds (i,j) =
  [ (i',j')
  | di <- [-1,0,1]
  , let i' = i+di
  , 0 <= i' && i' < sizeX
  , dj <- [-1,0,1]
  , let j' = j+dj
  , 0 <= j' && j' < sizeY
  , i' /= i || j' /= j
  ]

------------------------------------------------------------------------

